function Tictactoe(canvasElementId, containerElementId, params, setValueCallback) {
    if (typeof params === 'undefined') {
        params = {};
    }
    if (typeof setValueCallback !== 'function') {
        setValueCallback = function() {
        };
    }
    this.setValueCallback = setValueCallback;
    this.size = params.size || 11;
    this.scroll = params.scroll || false;
    this.one = params.one || {w: 30, h: 30};
    this.value = params.value || 1; // 1 - x , 2 - o
    
    this.moveCountToWin = 5;
    
    this.canvas = document.getElementById(canvasElementId);
    this.container = document.getElementById(containerElementId);
    this.initSize();
    this.ctx = this.canvas.getContext('2d');
    
    this.touchEvents = false;
    this.canvasState = null;
    this.moveLocked = false;
    this.lastMove = null;
    this.winMoves = [];
    this.winValue = null;
    this.elementToCalculateMoveField = this.container;
}

Tictactoe.prototype.autoAdjust = function() {
    var sizeMin = this.container.clientWidth;
    if (this.container.clientHeight < sizeMin) {
        sizeMin = this.container.clientHeight;
    }    
    
    this.canvas.className = 'center';
    this.scroll = false;
    this.elementToCalculateMoveField = this.canvas;
    this.size = Math.floor(sizeMin/this.one.w);
    // min size is 5
    if (this.size < 6) {
        this.size = 6;
    };
    this.initSize();
}

Tictactoe.prototype.initSize = function() {
    this.canvasWidth = this.canvas.width = this.size * this.one.w;
    this.canvasHeight = this.canvas.height = this.size * this.one.h;
    
    this.game = new Array();
    for (var x = 0; x < this.size; x++) {
        this.game[x] = new Array();
        for (var y = 0; y < this.size; y++) {
            this.game[x][y] = 0;
        }
    }
}


Tictactoe.prototype.getOponentValue = function() {
    if (this.value == 1) {
        return 2;
    }
    return 1;
}

Tictactoe.prototype.copyToSimple = function() {
    var res = new TictactoeSimple();
    res.size = this.size;
    res.game = this.game;
    
    return res;
}

Tictactoe.prototype.centerOnCail = function(x,y) {
    
    var height  = y * this.one.h;
    var width   = x * this.one.w;
    
    var scrollTop = (this.canvas.height - this.container.clientHeight) / (this.canvas.height / height);
    var scrollLeft = (this.canvas.width - this.container.clientWidth)  / (this.canvas.width / width);
    
    if (height * 100 / this.canvas.height > 75) {
        scrollTop = this.canvas.height;
    }
    
    if (height * 100 / this.canvas.height < 25) {
        scrollTop = 0;
    }
    
    if (width * 100 / this.canvas.width > 75) {
        scrollLeft = this.canvas.width;
    }
    
    if (width * 100 / this.canvas.width < 25) {
        scrollLeft = 0;
    }
    
    $(this.container).animate(
            {scrollTop : scrollTop, scrollLeft : scrollLeft}, 600
    );
}

Tictactoe.prototype.init = function(callback) {
    this.drawSurface();
    this.registerEvents();
    this.centerOnCail(this.size/2,this.size/2);
}

/**
 * Ustawia obiekt gry
 * 
 * @param {Array} game
 */
Tictactoe.prototype.setGame = function(game) {
    this.game = game;
    this.drawSurface();
}

/**
 * Zwraca obiekt gry
 * 
 * @returns {Array}
 */
Tictactoe.prototype.getGame = function() {
    return this.game;
}

/**
 * Ustawia wartosc w obiekcie gry
 * 
 * @param int x
 * @param int y
 */
Tictactoe.prototype.setValue = function(x, y) {
    if (this.moveLocked)
        return;
    
    if (this.game[x][y] !== 0) {
        return;
    }
    
    this.game[x][y] = this.value;
    
    this.setLastMove(x,y);
    
    this.drawSurface();
    
    this.setValueCallback(this.game, x, y, this.value);
}

Tictactoe.prototype.setOponentValue = function(x, y, value) {
   
   console.log(x, y, value);
    
    this.game[x][y] = value;
    
    this.setLastMove(x,y);
    
    this.drawSurface();
    
    if (this.scroll) {
        this.centerOnCail(x, y);
    }
}

/**
 * Zwraca wartosc w obiekcie gry
 * 
 * @param int x
 * @param int y
 * @returns int
 */
Tictactoe.prototype.getValue = function(x, y) {
    
    if (typeof this.game[x] === 'undefined') {
        return null;
    }
    
    if (typeof this.game[x][y] === 'undefined') {
        return null;
    }
    
    return this.game[x][y];
}

/**
 * Ustawia flage blokady ruchu
 * 
 * @param bool val
 */
Tictactoe.prototype.setMoveLocked = function(val) {
    this.moveLocked = val;
}

/**
 * Ustawia flage blokady ruchu
 * 
 * @param bool val
 */
Tictactoe.prototype.setLastMove = function(x, y) {
    this.lastMove = {'x': x, 'y': y};
}



Tictactoe.prototype.drawSurface = function () {
    
    // sureface
    var ctx = this.ctx;
    ctx.beginPath();
    // draw

    for (var x = 0; x < this.canvasWidth; x = x + this.one.w) {
        ctx.moveTo(x, 0);
        ctx.lineTo(x, this.canvasHeight);
    }
    for (var y = 0; y < this.canvasHeight; y = y + this.one.h) {
        ctx.moveTo(0, y);
        ctx.lineTo(this.canvasWidth, y);
    }
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 1;
    ctx.stroke();
    ctx.closePath();

    // moves
    for (var x = 0; x < this.size; x++) {
        for (var y = 0; y < this.size; y++) {

            if (this.getValue(x, y) === 1) {
                this.drawO(ctx, x, y);
            } else if (this.getValue(x, y) === 2) {
                this.drawX(ctx, x, y);
            }

        }
    }

    // borders 
    ctx.beginPath();
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 4;
    ctx.strokeRect(0, 0, this.canvasWidth, this.canvasHeight);
    ctx.closePath();
    
    
    
}






Tictactoe.prototype.registerEvents = function () {
    
    var t = this;
    
    if (this.touchEvents) {
        this.canvas.addEventListener('touchstart', function(e) {
            touchdown(e);
        }, true);
    } else {
        this.canvas.addEventListener('mousedown', function(e) {
            touchdown(e);
        }, true);
    }

    function touchdown(e) {
        
        /**
         * calculate move {x,y}
         */
        var move = t.getTouchedField(e);
        var mx = move.x;
        var my = move.y;

        // needed for touche events
        t.dragoffx = mx;
        t.dragoffy = my;
        t.dragging = true;
       

        // detecting if after 200ms the finger is still in the same position
        setTimeout(function() {
            if ((t.dragoffx === mx) && !t.dragging && (t.dragoffy === my)) {
                // Here you get the Tap event
                t.setValue(mx, my);
                console.log(mx, my);
            }
        }, 200);
    }
    
    
    
    
    
    
    
    
    if (this.touchEvents) {
        this.canvas.addEventListener('touchmove', function(e) {
            touchmove(e);
        }, true);
    } else {
        this.canvas.addEventListener('mousemove', function(e) {
            touchmove(e);
        }, true);
    }


    function touchmove(e) {
        if (t.dragging) {
            var move = t.getTouchedField(e);
            var mx = move.x;
            var my = move.y;
            // needed for touche events
            t.dragoffx = mx;
            t.dragoffy = my;
        }
    }









    if (this.touchEvents) {
        this.canvas.addEventListener('touchend', function(e) {
            touchup(e);
        }, true);
        this.canvas.addEventListener('touchleave', function(e) {
            touchup(e);
        }, true);
    } else {
        this.canvas.addEventListener('mouseup', function(e) {
            touchup(e);
        }, true);
        this.canvas.addEventListener('mouseout', function(e) {
            touchup(e);
        }, true);
    }


    function touchup(e) {
        t.dragging = false;
    }
}






Tictactoe.prototype.getTouchedField = function(e) {

    var element = this.elementToCalculateMoveField, offsetX = 0, offsetY = 0, mx, my;
    
    offsetX = element.scrollLeft;
    offsetY = element.scrollTop;

    offsetX -= element.offsetLeft;
    offsetY -= element.offsetTop;

    // mouse or touch event 
    var eventX = e.pageX || e.touches[0].clientX;
    var eventY = e.pageY || e.touches[0].clientY;

    mx = eventX + offsetX;
    my = eventY + offsetY;

    
    mx = Math.floor(mx / this.one.w);
    my = Math.floor(my / this.one.h);

    return {x: mx, y: my};
}




Tictactoe.prototype.isInLastMoves = function(x,y) {
    for(var i =0; i < this.winMoves.length; i++) {
        if (this.winMoves[i].x == x && this.winMoves[i].y == y) {
            return true;
        }
    }
    
    return false;
};


Tictactoe.prototype.drawO = function(ctx, x, y) {

    var cX = x * this.one.w;
    var cY = y * this.one.h;
    var r = Math.ceil(this.one.h / 2) - 1;

    ctx.beginPath();
    
    ctx.fillStyle = "#ffffff";
    if (this.lastMove && this.lastMove.x === x && this.lastMove.y === y) {
        ctx.fillStyle = "#f2f2f2";
    }
    
    if (this.isInLastMoves(x,y)) {
        ctx.fillStyle = "#ff6600";
    }
    
    ctx.fillRect(
        x * this.one.w + 1,
        y * this.one.h + 1,
        this.one.w - 2,
        this.one.h - 2
    );
    
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 1
    ctx.arc(cX + r, cY + r, r, 0, 2 * Math.PI);
    ctx.stroke();
    ctx.closePath();
};






Tictactoe.prototype.drawX = function(ctx, x, y) {
    ctx.beginPath();
    
    ctx.fillStyle = "#ffffff";
    if (this.lastMove && this.lastMove.x === x && this.lastMove.y === y) {
        ctx.fillStyle = "#f2f2f2";
    }
    
    if (this.isInLastMoves(x,y)) {
        ctx.fillStyle = "#ff6600";
    }
    
    ctx.fillRect(
        x * this.one.w + 1,
        y * this.one.h + 1,
        this.one.w - 2,
        this.one.h - 2
    );
    
    ctx.strokeStyle = "#000000";
    ctx.lineWidth = 1
    ctx.moveTo(x * this.one.w, y * this.one.h);
    ctx.lineTo(x * this.one.w + this.one.w, y * this.one.h + this.one.h);
    ctx.stroke();
    ctx.moveTo(x * this.one.w + this.one.w, y * this.one.h);
    ctx.lineTo(x * this.one.w, y * this.one.h + this.one.h);
    ctx.stroke();
    ctx.closePath();
};






Tictactoe.prototype.drawLastMove = function(ctx, x, y) {
    if (this.lastMove.x === x && this.lastMove.y === y) {
        console.log('last move : ', this.lastMove);
        
        ctx.beginPath();
        ctx.fillStyle = "#f2f2f2";
        ctx.fillRect(
            x * this.one.w + 1,
            y * this.one.h + 1,
            this.one.w - 2,
            this.one.h - 2
        );
        ctx.closePath();
    }
}




Tictactoe.prototype.getWinnerValue = function() {
    var t = this;
    function addMove(x,y) {
        t.winMoves.push({'x':x, 'y':y});
    }
    function clearMove() {
        t.winMoves = [];
    }
    
    var limit = 5;
    
    var value = null;
    
    var isMovePossible = false;
    
    /**
     * horizontal
     */
    for (var x = 0; x < this.size; x++) {
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var y = 0; y < this.size; y++) {
            value = this.getValue(x,y);
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(x,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(x,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
    }
    
    
    /**
     * vertical
     */
    for (var y = 0; y < this.size; y++) {
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var x = 0; x < this.size; x++) {
            value = this.getValue(x,y);
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(x,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(x,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
    }
    
    /**
     * left to right cross
     */
    var xx = null
    var yy = null;
    for (var x = 0; x < this.size; x++) {
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var y = 0; y < this.size; y++) {
            
            xx = y + x;
            
            value = this.getValue(xx,y);
            if (value === null) {
                wsk = 0;
                last = null;
                clearMove();
                continue;
            }
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(xx,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(xx,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var y = 0; y < this.size; y++) {
            
            xx = y - x;
            
            value = this.getValue(xx,y);
            if (value === null) {
                wsk = 0;
                last = null;
                clearMove();
                continue;
            }
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(xx,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(xx,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
        
    }
    
    /**
     * right to left cross
     */
    var xx = null
    var yy = null;
    for (var x = this.size - 1; x >= 0; x--) {
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var y = 0; y < this.size; y++) {
            
            xx = x - y;
            
            value = this.getValue(xx,y);
            if (value === null) {
                wsk = 0;
                last = null;
                clearMove();
                continue;
            }
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(xx,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(xx,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
        
        var wsk = 0;
        var last = null;
        clearMove();
        
        for (var y = 0; y < this.size; y++) {
            
            // metoda prob i bledow
            xx = x - y + 2 * (this.size - 1 - x);
            
            value = this.getValue(xx,y);
            if (value === null) {
                wsk = 0;
                last = null;
                clearMove();
                continue;
            }
            if (value != 0) {
                if ( value === last) {
                    wsk++;
                    addMove(xx,y);
                    if (wsk >= limit) {
                        return last;
                    }
                } else {
                    wsk = 1;
                    last = value;
                    clearMove();
                    addMove(xx,y);
                };
            } else {
                var wsk = 0;
                var last = null;
                isMovePossible = true;
                clearMove();
            }
        }
        
    }
    
    clearMove();
    
    if (isMovePossible === false) {
        return 3;
    }
    
    return null;
}